﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using Caliburn.Micro;
using Scrabble.Model;
using Scrabble.Tasks;

namespace Scrabble.ViewModels
{
    [Export(typeof(InputBoxViewModel)), PartCreationPolicy(CreationPolicy.NonShared)]
    public class InputBoxViewModel : PropertyChangedBase, IInputBox, IPlayingContext
    {
        private readonly IGame _game;
        private readonly IPlayer _player;

        private readonly SelectableCollection<PotentialPartOfPlay> _availableTiles = new SelectableCollection<PotentialPartOfPlay>();
        private readonly ObservableCollection<PotentialPartOfPlay> _takenTiles = new ObservableCollection<PotentialPartOfPlay>();

        private PlayVector _selectedArea;
        private IDisposable _verificationRequest;
        private bool _containsBadWords;
        private bool _isVerificationComplete;

        public InputBoxViewModel(IGame game, IPlayer player)
        {
            IoC.BuildUp(this);

            _player = player;

            _game = game;
            _game.GameOver += delegate { Refresh(); };

            _availableTiles.CollectionChanged += OnAvailableTilesChanged;
            _takenTiles.CollectionChanged += OnProposedPlayChanged;

            // FIX: if acitve player changes AND takentiles AND tiles on board intersect with input => clear

            ResetTiles();
        }

        [Import(typeof(ILexicon))]
        private ILexicon Lexicon
        {
            get;
            set;
        }

        [Import(typeof(IEventAggregator))]
        private IEventAggregator EventBroker
        {
            get; set;
        }

        public IGame Game
        {
            get { return _game; }
        }

        public IPlayer Player
        {
            get { return _player; }
        }

        public BoardLocation AnchorLocation
        {
            get { return SelectedArea == null ? null : SelectedArea.StartingLocation; }

            set
            {
                if (!_player.CurrentTiles.Any())
                {
                    SelectedArea = null;
                    return;
                }

                PlayVector[] candidates;

                if (SelectedArea == null)
                {
                    candidates = new[]
                                 {
                                     PlayVector.AcrossFrom(value),
                                     PlayVector.DownFrom(value)
                                 };
                }
                else
                {
                    var isAcross = SelectedArea.StartingLocation == value ? !SelectedArea.IsAcross : SelectedArea.IsAcross;

                    candidates = new[]
                                 {
                                     isAcross
                                         ? PlayVector.AcrossFrom(value)
                                         : PlayVector.DownFrom(value)
                                 };
                }

                foreach (var vector in candidates)
                {
                    var placed = 0;
                    var length = 0;

                    foreach (var location in vector.Locations)
                    {
                        ++length;

                        if (!_game.Board.TileExistsAt(location))
                            ++placed;

                        if (placed == _player.CurrentTiles.Count())
                            break;
                    }

                    var playVector = new PlayVector(vector) { Length = length };

                    SelectedArea = _game.IsPlacementValid(playVector) ? playVector : null;

                    if (SelectedArea != null) break;
                }
            }
        }

        public PlayVector SelectedArea
        {
            get { return _selectedArea; }
            private set
            {
                _selectedArea = value != null && value.Length > 0 ? value : null;
                NotifyOfPropertyChange(() => SelectedArea);
                ReturnAllTilesToRack();
            }
        }

        public bool ContainsBadWords
        {
            get { return _containsBadWords; }
            set
            {
                if (value != ContainsBadWords)
                {
                    _containsBadWords = value;
                    NotifyOfPropertyChange(() => ContainsBadWords);
                }

                _isVerificationComplete = true;
                NotifyOfPropertyChange(() => CanCommitPlay);
            }
        }

        #region Bound properties/methods

        public IEnumerable<PotentialPartOfPlay> AvailableTiles
        {
            get { return _availableTiles.Where(part => part.IsFree); }
        }

        public object TilesInProposedPlay
        {
            get { return _takenTiles; }
        }

        public int TotalScoreOfProposedPlay
        {
            get { return _takenTiles.Any() && SelectedArea != null ? _game.ScorePlacement(TakenTiles, SelectedArea) : 0; }
        }

        public void CancelPlay()
        {
            if (_takenTiles.Any())
                ReturnAllTilesToRack();
            else
                SelectedArea = null;
        }

        public bool CanSearchWord
        {
            get { return _takenTiles.Any(); }
        }

        public void SearchWord()
        {
            RequestSearch(SetofWordsFormedBy(TakenTiles).First(), true);
        }

        public void SuggestWords()
        {
            var searchText = new string(_availableTiles.OrderBy(part => part.PositionInRack).Select(part => part.Tile.Letter).ToArray());
            RequestSearch(searchText, false);
        }

        public bool CanCommitPlay
        {
            get { return Player.IsActive && _takenTiles.Any() && _isVerificationComplete && ! ContainsBadWords; }
        }

        public IResult CommitPlay()
        {
            try
            {
                PlayProposedWord();
                return null;
            }
            catch (Exception exception)
            {
                return Show.Notification(exception.Message);
            }
        }

        public bool CanPass
        {
            get { return Player.IsActive; }
        }

        public IResult Pass()
        {
            try
            {
                ReturnAllTilesToRack();
                Player.Pass();
                return null;
            }
            catch (Exception exception)
            {
                return Show.Notification(exception.Message);
            }
        }

        public bool CanExchange
        {
            get { return Player.IsActive && TilesToExchange.Any(); }
        }

        public IResult Exchange()
        {
            try
            {
                Player.ExchangeTiles(TilesToExchange.ToArray());
                ResetTiles();
                return null;
            }
            catch (Exception exception)
            {
                return Show.Notification(exception.Message);
            }
        }

        #endregion

        //public event EventHandler Activated;

        //public void Activate()
        //{
        //    var handler = Activated;
        //    if (handler != null) Activated(this, EventArgs.Empty);
        //}

        public void PlayTileFromRack(char letter)
        {
            if (SelectedArea.Length == _takenTiles.Count) return;

            var nextLocation = _game.Board.AvailableLocationsIn(SelectedArea).Skip(_takenTiles.Count).FirstOrDefault();
            if (nextLocation == null) return;

            var tileToPlay =
                AvailableTiles.FirstOrDefault(part => part.Tile.Letter == letter) ??
                AvailableTiles.FirstOrDefault(part => part.Tile.InitiallyBlank);

            if (tileToPlay == null) return;
            tileToPlay.Location = nextLocation;

            if (tileToPlay.Tile.InitiallyBlank)
                tileToPlay.Tile.UseBlankAs(letter);

            _takenTiles.Add(tileToPlay);
        }

        public void ReturnLastTileToRack()
        {
            if (_takenTiles.Any()) ReturnTileToRack(_takenTiles.Last());
        }

        public void ReturnAllTilesToRack()
        {
            while (_takenTiles.Any()) ReturnTileToRack(_takenTiles.First());
        }

        public void PlayProposedWord()
        {
            if (! CanCommitPlay) return;

            _player.PlaceTiles(TakenTiles, SelectedArea);
            ResetTiles();
            SelectedArea = null;
        }

        public IEnumerable<Tile> TilesToExchange
        {
            get { return AvailableTiles.Where(part => part.IsSelected).Select(part => part.Tile); }
        }

        private IEnumerable<Tile> TakenTiles
        {
            get { return _takenTiles.Select(part => part.Tile); }
        }

        private void ReturnTileToRack(PotentialPartOfPlay part)
        {
            part.Location = null;
            _takenTiles.Remove(part);
        }

        private void RequestSearch(string searchText, bool isPrefix)
        {
            EventBroker.Publish(new SearchRequest
            {
                SearchWord = searchText,
                SearchWordIsPrefix = isPrefix
            });
        }

        private void ResetTiles()
        {
            ContainsBadWords = false;

            _availableTiles.Clear();
            _takenTiles.Clear();

            foreach (var tile in _player.CurrentTiles)
                _availableTiles.Add(new PotentialPartOfPlay { Tile = tile });
        }

        private void OnSelectionChanged(object sender, EventArgs e)
        {
            NotifyOfPropertyChange(() => CanExchange);
        }

        private void OnAvailableTilesChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (PotentialPartOfPlay part in e.NewItems)
                        part.SelectionChanged += OnSelectionChanged;
                    break;

                case NotifyCollectionChangedAction.Remove:
                    foreach (PotentialPartOfPlay part in e.OldItems)
                        part.SelectionChanged -= OnSelectionChanged;
                    break;
            }
        }

        private void OnProposedPlayChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            RequestVerification();

            NotifyOfPropertyChange(() => CanExchange);
            NotifyOfPropertyChange(() => CanSearchWord);
            NotifyOfPropertyChange(() => CanCommitPlay);
            NotifyOfPropertyChange(() => TotalScoreOfProposedPlay);
            NotifyOfPropertyChange(() => AvailableTiles);
        }

        private void RequestVerification()
        {
            if (_verificationRequest != null)
            {
                _verificationRequest.Dispose();
                _verificationRequest = null;
            }

            if (! _takenTiles.Any())
            {
                ContainsBadWords = false;
            }
            else
            {
                _isVerificationComplete = false;

                _verificationRequest = Observable.Timer(TimeSpan.FromMilliseconds(300)).Take(1)
                    .ObserveOnDispatcher()
                    .Subscribe(result => { ContainsBadWords = !SetofWordsFormedBy(TakenTiles).All(word => Lexicon.Contains(word)); });
            }
        }

        private IEnumerable<string> SetofWordsFormedBy(IEnumerable<Tile> takenTiles)
        {
            return ! takenTiles.Any() 
                ? Enumerable.Empty<string>() 
                : _game.SetOfWordsFormedBy(takenTiles, SelectedArea).Select(word => new String(word.Select(part => part.Tile.Letter).ToArray()));
        }
    }
}
